Обновить

Import substitution in I2P: signature according to GOST R 34.10-2012

Время на прочтение 4 min
Количество просмотров 12K
Elliptic cryptography, having high strength and widespread use, has always caused a lot of controversy and speculation about possible bookmarks for different curves and signature schemes. However, no one could give an example of such a bookmark or prove their absence. Therefore, unlike symmetric cryptography, where the leadership unconditionally belongs to AES, asymmetric cryptography is used in different types, depending on preferences, technical or legal requirements. Additional types of address signatures in I2P provide more choice and flexibility for applications. GOST is supported in openssl via the EVP interface, but in version 1.1 it is excluded from the standard delivery; in addition, the existing implementation involves storing and transmitting public keys and signatures in the DER format, and I2P works directly with numbers, determining the necessary parameters from the signature type.

Types of signatures in I2P


Currently in I2P there is 9 types of signatures, where the signature type is specified, then the hash type and, if necessary, the curve or its set of parameters.

  1. ECDSA_SHA256_P256
  2. ECDSA_SHA384_P384
  3. ECDSA_SHA512_P521
  4. RSA_SHA256_2048
  5. RSA_SHA384_3072
  6. RSA_SHA512_4096
  7. EdDSA_SHA512_Ed25519
  8. EdDSA_SHA512_Ed25519ph

Also, old addresses use type 0 - DSA_SHA1, which is considered obsolete.
It is recommended to use types 1 and 7.

For GOST, at my request, we have been allocated two types:
9 — GOSTR3410_GOSTR3411_256_CRYPTO_PRO_A
10 — GOSTR3410_GOSTR3411_512_TC26_A
for 256 and 512-bit keys respectively.

The length of the public key for 9 is 64 bytes (32 bytes for each point coordinate) and 128 bytes for 10.

Implementation of GOST R signature 34.10


It is assumed that a hash of 256 or 512 bits is signed and verified. Described in detail and with examples Here.

A regular elliptic curve is used, so functions from the cryptographic library can be used to work with it. In particular, these are EC_GROUP_* and EC_POINT_* from openssl, the main one of which is:

int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx)

used both to multiply a base point by a number and an arbitrary point, depending on the parameters.

For the curve, 6 parameters are set: p - modulus, a and b - coefficients of the curve equation, P(x,y) - base point, q - prime number, multiplying by which the base point gives zero.

p and q must be large and close to the maximum, since p limits the range of all numbers, and q limits the range of the secret keys. Base point and q are calculated simultaneously.
As a rule, well-known and well-tested parameter sets are used.

In our implementation we will use 2 sets of parameters:

  • GostR3410_2001_CryptoPro_A_ParamSet (OID 1.2.643.2.2.35.1) for 256-bit keys, borrowed from GOST R 34.10-2001;
  • id-tc26-gost-3410-12-512-paramSetA (OID 1.2.643.7.1.2.1.2.1) for 512-bit keys, developed by TK26 (tc26.ru) specifically for GOST R 34.10-2012.

Unlike a curve, GOST has its own signature scheme, so the ECDSA_sign and ECDSA_verify functions cannot be used and you should implement signature and signature verification in your code.

For the signature (r,s), a random number k is selected, and the point R=k*P is calculated, the x coordinate of which becomes the r component of the signature. Component s = r*d + h*k, where d is the secret key, h is the hash of the message signature in Big Endian.

To check the signature, multiply both sides of the equality by the base point P.

Indeed, s*P = r*d*P + h*k*P = r*Q + h*R, where Q is the public key. In this equation we do not know the point R, and although it is possible to reconstruct the y coordinate from r, this is an extremely slow operation. Therefore, we rewrite the equality in the form h*R = s*P - r*Q, then
R = (s*P -r *Q)/h and compare only the x coordinate.

In i2pd code it looks like this
bool GOSTR3410Curve::Verify (const EC_POINT * pub, const BIGNUM * digest, const BIGNUM * r, const BIGNUM * s)
{
	BN_CTX * ctx = BN_CTX_new ();
	BN_CTX_start (ctx);
	BIGNUM * q = BN_CTX_get (ctx);
	EC_GROUP_get_order(m_Group, q, ctx);
	BIGNUM * h = BN_CTX_get (ctx);
	BN_mod (h, digest, q, ctx); // h = digest % q
	BN_mod_inverse (h, h, q, ctx); // 1/h mod q
	BIGNUM * z1 = BN_CTX_get (ctx);
	BN_mod_mul (z1, s, h, q, ctx); // z1 = s/h
	BIGNUM * z2 = BN_CTX_get (ctx);				
	BN_sub (z2, q, r); // z2 = -r
	BN_mod_mul (z2, z2, h, q, ctx); // z2 = -r/h
	EC_POINT * C = EC_POINT_new (m_Group);
	EC_POINT_mul (m_Group, C, z1, pub, z2, ctx); // z1*P + z2*pub
	BIGNUM * x = BN_CTX_get (ctx);	
	GetXY  (C, x, nullptr); // Cx
	BN_mod (x, x, q, ctx); // Cx % q
	bool ret = !BN_cmp (x, r); // Cx = r ?
	EC_POINT_free (C);
	BN_CTX_end (ctx);
	BN_CTX_free (ctx);
	return ret;
}	


Hash function GOST R 34.11-2012 (Stribog)


Although to sign a message you can use any function that calculates a hash of a suitable size, for example SHA256/SHA512, we will use the one prescribed by the GOST R 34.11-2012 standard, including for compatibility with existing implementations. Unlike a signature, a hash is much simpler.

Detailed description and examples. Let's note the main points:

  • The original message is divided into blocks of 64 bytes. If the message is not a multiple of 64, then the first block is padded on the left with zero and one bytes.
  • Each block is hashed separately based on the hash of the previous block. For the very first block, the initialization vector(iv) is set as the previous hash. The blocks are passed through in reverse order — from last to first.
  • Each block is "encrypted" 12 rounds with given keys, each round is the application of three transformations S, P, L using given tables and XOR with the round key.
  • S represents a byte-by-byte replacement, P is a transpose of the hash as an 8x8 matrix, L is a multiplication by a given matrix.
  • 256-the bit hash is the left half of the 512-bit hash, but a different iv is used, so they cannot be calculated at the same time.

Signing by an external crypto provider using the I2CP protocol


If the address connects to the router using the protocol I2CP, then the router does not need to know the secret signing key.

Instead, the router sends a RequestLeaseSetMessage (or RequestVariableLeaseSetMessage), expecting in return a CreateLeaseSetMessage containing a LeaseSet signed with the address's secret key. As you can see from the protocol description, in older versions of I2P it was required to transmit this key in a message, this is no longer required.
Thus, an application that implements an I2P address can use the API of one of the existing crypto providers with GOST for signing, allowing you to effectively integrate an I2P solution into the existing infrastructure.

Implementation


Currently i2pd fully supports signature types 9 and 10. Any client addresses will work with addresses on i2pd. example use. For server addresses to work, support from floodfills is required, or a network independent of the main I2P can be built with a netid different from 2. In the main network, you need to wait for this to happen implemented in Java or additional floodfill parameter.
Tags:
Hubs:
Всего голосов 21: ↑19 и ↓2 +17
Комментарии 8
+8

Comments 8

Beautiful. But an additional hub “Abnormal Programming” would be useful here.

A UFO flew in and published this inscription here
I'd rather read it anyway standard, in which it is written in black and white “calculate the subvector m of the message M=M'||m”. And all implementations (possibly except yours) go in reverse order.
Ivan_83 is still right. At a minimum, it would simply be illogical: no one needs a hash that is calculated over the data in reverse order, since this simply would not allow streaming data processing. 11/34/2012 is available in https://tools.ietf.org/html/rfc6986.html#section-10 where examples show how it should be calculated. What's confusing is that authors like to write *examples* in reverse order. All those used in “combat”, in practice, implementations are done as described by Ivan_83.
CryptoPro, however, has a different opinion and calculates exactly as described in the standard.
This is easy to verify, using their utility for the example file.
Hmm, maybe I misunderstood Ivan_83. The CryptoPro utility that you showed makes hashes in the same way as for example (I’m just their author) http://pygost.cypherpunks.ru/ and http://gogost.cypherpunks.ru/. That is, from my point of view, everything is correct, as expected. If everything is the same for you as for her, then everything is correct for you too. I just looked at your C++ code in which memcpy is done for the 256-bit version of Stribog - I’m not a C programmer and I thought that the first 32 bytes from the hash are “cut off” and this becomes the return value of the function. Either I misunderstood memcpy or something else is “twisting” somewhere, but the CryptoPro utility is really correct.
A UFO flew in and published this inscription here
the existing implementation involves storing and transmitting public keys and signatures in DER format

This is something new for openssl. Who prevents you from working with PEM,!

Only full-fledged users can leave comments. Sign in, Please.